From 11e0bee504c643e94e54ad6b66185a0164842a5c Mon Sep 17 00:00:00 2001 From: Paul Donald Date: Wed, 11 Jun 2025 23:52:01 +0200 Subject: [PATCH] file: use lstat for file list (instead of stat) to reveal links I don't think there was a conscious design choice for stat(), but it is documented that the 'type' entry using the rpcd file mechanisms can be 'symlink' but this is never the case using stat(). lstat and stat are virtually identical, but that lstat reports that a symlink entry is a symlink, whereas stat reports that a symlink entry is a file (the file it points to) and info about the link target instead. Someone parsing a directory using stat() would never know about links. Using lstat, we can see whether an entry is a symlink for no extra cost. When the file list encounters a link entry, it now includes the element "target_type" which contains the link target type: "file", "directory" etc - or "broken" if the link is broken - via one additional stat call. So when calling e.g.: ubus call file list '{"path":"/etc"}' instead of: ... "name": "os-release", "type": "file", "size": 610, "mode": 33188, "atime": 1749462226, "mtime": 1749462226, "ctime": 1749462226, "inode": 894, "uid": 0, "gid": 0 ... We get: ... "name": "os-release", "type": "symlink", "size": 21, "mode": 41471, "atime": 1749462226, "mtime": 1749462226, "ctime": 1749462226, "inode": 234, "uid": 0, "gid": 0, "target_type": "file" ... Moved the type determination out of _rpc_file_add_stat to a new function _get_stat_type for re-usability in the if (S_ISLNK(s.st_mode)) block. Tested on: openwrt main Signed-off-by: Paul Donald Tested-by: Eric Fahlgren Link: https://github.com/openwrt/rpcd/pull/13 Signed-off-by: Robert Marko --- file.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/file.c b/file.c index f14b274..dc9c88a 100644 --- a/file.c +++ b/file.c @@ -470,8 +470,8 @@ rpc_file_md5(struct ubus_context *ctx, struct ubus_object *obj, return UBUS_STATUS_OK; } -static void -_rpc_file_add_stat(struct stat *s) +static int +_get_stat_type(struct stat *s) { int type; @@ -483,8 +483,13 @@ _rpc_file_add_stat(struct stat *s) S_ISLNK(s->st_mode) ? DT_LNK : S_ISSOCK(s->st_mode) ? DT_SOCK : DT_UNKNOWN; + return type; +} - blobmsg_add_string(&buf, "type", d_types[type]); +static void +_rpc_file_add_stat(struct stat *s) +{ + blobmsg_add_string(&buf, "type", d_types[_get_stat_type(s)]); blobmsg_add_u32(&buf, "size", s->st_size); blobmsg_add_u32(&buf, "mode", s->st_mode); blobmsg_add_u32(&buf, "atime", s->st_atime); @@ -523,11 +528,22 @@ rpc_file_list(struct ubus_context *ctx, struct ubus_object *obj, if (asprintf(&entrypath, "%s/%s", path, e->d_name) < 0) continue; - if (!stat(entrypath, &s)) + // Use lstat to detect symlinks + if (!lstat(entrypath, &s)) { d = blobmsg_open_table(&buf, NULL); blobmsg_add_string(&buf, "name", e->d_name); _rpc_file_add_stat(&s); + + // add target type only for symlinks + if (S_ISLNK(s.st_mode)) { + struct stat target; + if (!stat(entrypath, &target)) { + blobmsg_add_string(&buf, "target_type", d_types[_get_stat_type(&target)]); + } else { + blobmsg_add_string(&buf, "target_type", "broken"); + } + } blobmsg_close_table(&buf, d); } -- 2.30.2